package org.processmining.plugins.complianceframework.visualization;

import org.processmining.plugins.complianceframework.ComplianceDashboard;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Font;
import java.awt.FontMetrics;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import javax.swing.JPanel;
import javax.swing.table.TableColumn;

public class DashboardGraphPanel extends JPanel {
	private static final int barGap = 2;
	private static final int horizontalBorderGap = 20;
	private static final int verticalTopBorderGap = 30;
	private static final int verticalBottomBorderGap = 50;
	private static final int maxColorIntensity = 150;
	private static final int minFontSize = 8;
	// private static final int minWordGap = 8;

	private ComplianceDashboard dashboard;
	private String visualizationMode;
	private String visualizationValue;
	private boolean showCaseFrequency;
	private String[] captions;
	private Map<String, Color> colorMap;

	// private Map<String, Integer> heightMap;

	public DashboardGraphPanel(ComplianceDashboard dashboard,
			String visualizationMode, String visualizationValue,
			boolean showCaseFrequency) {
		super();
		init(dashboard, visualizationMode, visualizationValue,
				showCaseFrequency);
	}

	public void init(ComplianceDashboard dashboard, String visualizationMode,
			String visualizationValue, boolean showCaseFrequency) {
		this.dashboard = dashboard;
		this.visualizationMode = visualizationMode;
		this.visualizationValue = visualizationValue;
		this.showCaseFrequency = showCaseFrequency;
		this.captions = setupCaptions(this.dashboard);
		this.colorMap = setupColors(this.dashboard);
		this.repaint();
	}

	@Override
	public void paint(Graphics g) {
		super.paint(g);
		drawBarChart(g);
	}

	private void drawBarChart(Graphics g) {
		g.setColor(new Color(0, 150, 0));
		int totalWidth = getWidth() - horizontalBorderGap * 2;
		Map<String, Integer> heightMap = setupHeightMap(this.captions);
		int barWidth = (int) ((float) totalWidth / (float) this.captions.length);
		Font captionFont = setupCaptionFont(g, barWidth);
		Font percentageFont = setupPercentageFont(g, barWidth);
		FontMetrics metrics = g.getFontMetrics(captionFont);
		for (int i = 0; i < this.captions.length; i++) {
			int maxHeight = getHeight()
					- (verticalTopBorderGap + verticalBottomBorderGap);
			g.setColor(this.colorMap.get(captions[i]));

			g.fillRect(horizontalBorderGap + i * barWidth + barGap, getHeight()
					- (verticalBottomBorderGap + heightMap
							.get(this.captions[i]).intValue()), barWidth
					- barGap * 2, heightMap.get(this.captions[i]).intValue());
			g.setColor(Color.BLACK);
			g.drawRect(horizontalBorderGap + i * barWidth + barGap, getHeight()
					- (verticalBottomBorderGap + heightMap
							.get(this.captions[i]).intValue()), barWidth
					- barGap * 2, heightMap.get(this.captions[i]).intValue());
			String percentage = (Math.round(((float) heightMap.get(this.captions[i]).intValue() / (float) maxHeight) * 100.0f)) + "%";
			
			g.setFont(percentageFont);
			g.drawString(percentage,
					horizontalBorderGap + i * barWidth + barWidth / 2 + barGap
					- metrics.stringWidth(percentage) / 2, getHeight()
					- (verticalBottomBorderGap + heightMap
							.get(this.captions[i]).intValue()) - 5);

			// TODO: consider multiline and too many captions
			g.setFont(captionFont);
			g.drawString(this.captions[i],
					horizontalBorderGap + i * barWidth + barWidth / 2 + barGap
							- metrics.stringWidth(this.captions[i]) / 2,
					getHeight() - verticalBottomBorderGap + metrics.getHeight());
		}
	}

	private Font setupPercentageFont(Graphics g, int barWidth) {
		int fontSize = minFontSize - 1;
		Font result;
		while (true) {
			fontSize++;
			result = new Font(g.getFont().getName(), Font.PLAIN, fontSize);
			FontMetrics metrics = g.getFontMetrics(result);
			if (metrics.stringWidth("100%") >= barWidth
					|| metrics.getHeight() >= verticalTopBorderGap) {
				result = new Font(g.getFont().getName(), Font.PLAIN, fontSize - 1);
				break;
			}
		}

		return result;
	}
	
	private Font setupCaptionFont(Graphics g, int barWidth) {
		int fontSize = minFontSize - 1;
		Font result;
		while (true) {
			fontSize++;
			result = new Font(g.getFont().getName(), Font.PLAIN, fontSize);
			FontMetrics metrics = g.getFontMetrics(result);
			if (!captionsFit(metrics, barWidth)
					|| metrics.getHeight() >= verticalBottomBorderGap) {
				result = new Font(g.getFont().getName(), Font.PLAIN, fontSize - 1);
				break;
			}
		}

		return result;
	}

	private boolean captionsFit(FontMetrics metrics, int barWidth) {
		boolean result = true;
		for (int i = 0; i < captions.length; i++) {
			String[] words = captions[i].split(" ");
			// int maxWidth = Integer.MIN_VALUE;
			// for (int j = 0; j < words.length; j++) {
			// int width = metrics.stringWidth(words[j]);
			// if (maxWidth < width)
			// maxWidth = width;
			// }
			int maxWidth = metrics.stringWidth(captions[i]);
			if (maxWidth > barWidth) {
				result = false;
				break;
			}
		}
		return result;
	}

	private Map<String, Color> setupColors(ComplianceDashboard dashboard) {
		Map<String, Color> result = new HashMap<String, Color>();
		result.put(this.captions[0], new java.awt.Color(80, 80, 80));
		int maxEls = dashboard.getCombinations()
				.get(dashboard.getCombinations().size() - 1).size();
		float intensity = (0.5f / (float) maxEls) * maxColorIntensity;
		// sync moves
		result.put(this.captions[1],
				new java.awt.Color(0, Math.round(intensity), 0));
		// sync moves with correct & incorrect params
		for (int i = 0; i < dashboard.getCombinations().size(); i++) {
			intensity = ((float) dashboard.getCombinations().get(i).size() / (float) maxEls)
					* maxColorIntensity;
			result.put(this.captions[i + 2],
					new java.awt.Color(0, Math.round(intensity), 0));
			result.put(
					this.captions[dashboard.getCombinations().size() + i + 2],
					new java.awt.Color(Math.round(intensity), 0, 0));
		}
		// async moves
		result.put(this.captions[this.captions.length - 2], new java.awt.Color(
				maxColorIntensity, 0, 0));
		result.put(this.captions[this.captions.length - 1], new java.awt.Color(
				maxColorIntensity, 0, 0));

		return result;
	}

	private Map<String, Integer> setupHeightMap(String[] captions) {

		int maxHeight = getHeight()
				- (verticalTopBorderGap + verticalBottomBorderGap);
		Map<String, Integer> result = new HashMap<String, Integer>();
		if (visualizationMode.equals("Activity")) {
			int[] activityStats;
			if (!showCaseFrequency)
				activityStats = dashboard.getActivityMovesMap().get(
						visualizationValue);
			else
				activityStats = dashboard.getActivityCaseFreqMap().get(
						visualizationValue);
			for (int i = 0; i < activityStats.length; i++) {
				int totalMoves = activityStats[0] + activityStats[1]
						+ activityStats[activityStats.length - 2];
				int height = Math.round(((float) maxHeight
						* (float) activityStats[i] / (float) totalMoves));
				result.put(captions[i], height);
			}
		} else {
			int[] moveStats;
			if (!showCaseFrequency)
				moveStats = dashboard.getAttributeMovesMap()
						.get(visualizationMode).get(visualizationValue);
			else
				moveStats = dashboard.getAttributeCaseFreqMap()
						.get(visualizationMode).get(visualizationValue);
			for (int i = 0; i < moveStats.length; i++) {
				int totalMoves = moveStats[0] + moveStats[1]
						+ moveStats[moveStats.length - 2];
				int height = Math.round(((float) maxHeight
						* (float) moveStats[i] / (float) totalMoves));
				result.put(captions[i], height);
			}
		}
		return result;
	}

	private String[] setupCaptions(ComplianceDashboard dashboard) {
		String[] result = new String[dashboard.getCombinations().size() * 2 + 4];
		result[0] = "Invisible moves";
		result[1] = "Synchronous moves";
		result[dashboard.getCombinations().size() * 2 + 2] = "Log-only moves";
		result[dashboard.getCombinations().size() * 2 + 3] = "Model-only moves";
		for (int i = 0; i < dashboard.getCombinations().size(); i++) {
			if (i == (dashboard.getCombinations().size() - 1)
					&& dashboard.getCombinations().get(i).size() > 2) {
				result[i + 2] = "All aspects correct";
				result[dashboard.getCombinations().size() + i + 2] = "All aspects inccorrect";
			} else {
				String fieldName = "";
				for (int j = 0; j < dashboard.getCombinations().get(i).size(); j++) {
					fieldName += dashboard.getCombinations().get(i).get(j);
					if (j < dashboard.getCombinations().get(i).size() - 1)
						fieldName += ", ";
				}
				result[i + 2] = "Correct " + fieldName;
				result[dashboard.getCombinations().size() + i + 2] = "Incorrect "
						+ fieldName;
			}
		}

		return result;
	}
}
